//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================
//==========================================================================
// Copyright (c) 2000-2004,  Elastos, Inc.  All Rights Reserved.
//**author:hanxu
//**y/m/d:2005/5/16
//==========================================================================

#include <ddk.h>
#include "i8042.h"
#include "ps2mouse.h"
#include "ite8172.h"
#include "register.h"

static bool_t Ps2MouseSendCommand(ioport_t Base, uint8_t Cmd);

#define SIZEOF_REPORT 3

typedef union {
    uint32_t    i;      /* quick compare      */
    uint8_t     u[3];   /* Pick one at a time */
} MouseData;

//
// Mouse
//
class Mouse : public CDeviceDriver
{
public:
    CARAPI Read(
        /* [in] */ UInt64 u64Offset,
        /* [in] */ UInt32 bytesToRead,
        /* [out] */ MemoryBuf * pBuffer,
        /* [out] */ IEvent * * ppCompletionEvent);

    CARAPI Write(
        /* [in] */ UInt64 u64Offset,
        /* [in] */ const MemoryBuf & buffer,
        /* [out] */ UInt32 * pBytesWritten,
        /* [out] */ IEvent * * ppCompletionEvent);

    CARAPI Control(
        /* [in] */ Handle32 nControlCode,
        /* [in] */ const MemoryBuf & inBuffer,
        /* [out] */ MemoryBuf * pOutBuffer,
        /* [out] */ IEvent * * ppCompletionEvent);

    virtual void Dispose();

public:
    Mouse()
    {
        m_currData.i=0x00000000;
    }
    void Isr();

    ECode Initialize();

public:
    DzEvent       m_receiveEvent;
    MouseData   m_currData;
    MouseData   m_nextData;
    ioport_t    m_uPortBase;
    uint8_t     m_uIrq;
};

ECode Mouse::Read(
    /* [in] */ UInt64 u64Offset,
    /* [in] */ UInt32 bytesToRead,
    /* [out] */ MemoryBuf * pBuffer,
    /* [out] */ IEvent * * ppCompletionEvent)
{
    if (pBuffer == NULL || pBuffer->GetCapacity() < SIZEOF_REPORT) {
        return E_INVALID_ARGUMENT;
    }

    /* Wait for some to be available */
    WaitResult wr;
    m_receiveEvent.Wait(&wr, NULL);
    if (WaitResult_OK != wr) return NOERROR;

    pBuffer->Copy((Byte *)m_currData.u, 3);
    return NOERROR;
}

ECode Mouse::Write(
    /* [in] */ UInt64 u64Offset,
    /* [in] */ const MemoryBuf & buffer,
    /* [out] */ UInt32 * pBytesWritten,
    /* [out] */ IEvent * * ppCompletionEvent)
{
    return E_NOT_IMPLEMENTED;
}

ECode Mouse::Control(
    /* [in] */ Handle32 nControlCode,
    /* [in] */ const MemoryBuf & inBuffer,
    /* [out] */ MemoryBuf * pOutBuffer,
    /* [out] */ IEvent * * ppCompletionEvent)
{
    return E_NOT_IMPLEMENTED;
}

void Mouse::Dispose()
{
    delete this;
}

EXTERN_C void PrintComPort(const char *szMessage);

INLINE void Mouse::Isr()
{
    UInt8 c;
    const ioport_t iobase = m_uPortBase;

    /* Sanity check */
    if (!MOUSE_RECEIVE_INTERRUPT(iobase)) return;

    /* Pick up the byte */
    c = i8042_READ_DATA_PORT(iobase);
    m_nextData.u[m_nextData.u[3]++] = c;

    /* Report complete ?
     * BUGBUG dropped bytes
     */
    if (m_nextData.u[3] == 3) {

        /* Yes, copy report */
        m_currData.i = m_nextData.i;
        m_nextData.u[3] = 0;

        /* Synchronize with reader */
        m_receiveEvent.NotifyByIsr();
    }
}

STATIC void CDECL MouseIsr(
    irq_t irq, void *pvDevice, InterruptContext *pContext)
{
    assert(pvDevice);
    ((Mouse *)pvDevice)->Isr();
}

ECode Mouse::Initialize()
{
    int resetInfo = 0;
    ECode ec;

    m_uPortBase = 0xb4000060;
    m_uIrq = 12;

    const ioport_t iobase = m_uPortBase;

    m_nextData.i = 0;

    kprintf("entering mouse initialise....\n");

    /*   BUG:  i don't know why , but i must add it to disable
     *         keyboard interrupt, use in some signle board computer
     */
    KbdDisableReceiveInterrupt(iobase);

    /* Disable interrupts from aux device */
    MOUSE_DISABLE_INTERRUPTS(iobase);

    /* Enable aux device */
//    KSendCommand(iobase, PS2AUX_DISABLE_CONTROLLER);
    MOUSE_ENABLE(iobase);

    /* Reset mouse. If present. */
    if (!Ps2MouseSendCommand(iobase, PS2AUX_RESET)) {
        MOUSE_DISABLE(iobase);
        kprintf("Failed to find the mouse 1\n");

        KbdEnableReceiveInterrupt(iobase);

        ec = E_FAIL;
        return ec;
    }

    /* Mouse sends back two bytes, check they are right */
    MRecv(iobase, resetInfo);
    if (resetInfo != PS2AUX_BAT) {
        MOUSE_DISABLE(iobase);
        kprintf("Failed to find the mouse 2\n");

        KbdEnableReceiveInterrupt(iobase);

        ec = E_FAIL;
        return ec;
    }
    else {
        MRecv(iobase, resetInfo);
        if (resetInfo != 0x00) {
            MOUSE_DISABLE(iobase);
            kprintf("Failed to find the mouse 3\n");

            KbdEnableReceiveInterrupt(iobase);

            ec = E_FAIL;
            return ec;
        }
    }

    /* Set mouse in stream mode */
    Ps2MouseSendCommand(iobase, PS2AUX_SET_STREAM);

    /* Enable mouse transmission */
    Ps2MouseSendCommand(iobase, PS2AUX_ENABLE_DEVICE);

    /* set resolutionset mouse resolution
     * (00h = 1/mm,01h = 2/mm,02h = 4/mm,03h = 8/mm)
     */
    Ps2MouseSendCommand(iobase, PS2AUX_SET_RESOLUTION);
    Ps2MouseSendCommand(iobase, 0x02);

    /* The NT driver instead does:
     *           enable aux
     *   reset and get reply
     *   query number of buttons
     *  set sampling rate (0xf3) to 60.
     *  set mouse resolution (0xe8) to <whatever>
     */

    Ps2MouseSendCommand(iobase, PS2AUX_SET_SAMPLE);
    Ps2MouseSendCommand(iobase, 60);

    /* All seems fine, enable interrupts and return */
    MOUSE_ENABLE_INTERRUPTS(iobase);

    kprintf("Found the mouse\n");

    uint32_t uFlags = SaveFlagsAndCli();

    // Register the ISR.
    AddIt8172Device(m_uIrq, &MouseIsr, this);

    RestoreIF(uFlags);

    ec = NOERROR;

    KbdEnableReceiveInterrupt(iobase);

    kprintf(" mouse driver initialised OK!\n");

    return ec;
}

EXTERN IDeviceDriver * CDECL CreateMouse(uint_t uDeviceNo, void *pvParameter)
{
    Mouse *pMouse = new Mouse();
    if (NULL == pMouse) return NULL;

    ECode ec = pMouse->Initialize();
    if (FAILED(ec)) {
        delete pMouse;
        return NULL;
    }

    pMouse->AddRef();
    return pMouse;
}

/* Utilities */
static bool_t Ps2MouseSendCommand(ioport_t Base, uint8_t Cmd)
{
    int i;

    /* write to aux */
    KSendCommand(Base, PS2AUX_WRITE_MAGIC);
    KSendData(Base, Cmd);

    /* Mouse might not be there, spin for a while waiting for ack */
    for (i = 0; i < 12000; i++) {
        DzDelay(200);
        if ((i8042_READ_STATUS_PORT(Base) & PS2AUX_OUTBUF_FULL)
            == PS2AUX_OUTBUF_FULL)
            break;
    }

    if (i == 12000) return FALSE;
    KRecv(Base,i);

    if (i != PS2AUX_ACK) {
        kprintf("Bad mouse ack %x to cmd %x\n", i, Cmd);
        return FALSE;
    }

    return TRUE;
}
